#include <iostream>
#include <cstdio>
#include <vector>
#include <unistd.h>
#include <functional>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
///////////////////////子进程任务清单///////////////////////////

void task1()
{
    printf("finish task1\n");
    sleep(1); // 模拟需要完成的时间
}

void task2()
{
    printf("finish task2\n");
    sleep(1); // 模拟需要完成的时间
}

void task3()
{
    printf("finish task3\n");
    sleep(1); // 模拟需要完成的时间
}

void task4()
{
    printf("finish task4\n");
    sleep(1); // 模拟需要完成的时间
}
typedef void (*task)(); // 定义函数指针类型
task task_list[4] = {task1, task2, task3, task4};
///////////////////////进程池////////////////////////////
enum
{
    OK = 0,
    PIPE_FAILD,
    FORK_FAILD,
    READ_FAILD
};
int processnum = 5;

// typedef void(*cb_t)(int);
using cb_t = std::function<void(int)>;
// 子进程进入的地方
void Do_Task(int fd) // 子进程内部调用的回调函数
{
    while (true)
    {
        // read读数据
        int TaskIndex = 0;
        ssize_t n = read(fd, &TaskIndex, sizeof TaskIndex);
        // std::cout << "---" << n << "---" << sizeof(TaskIndex) << "---" << std::endl; // 44字节
        if (n > 0 && n == sizeof TaskIndex) // 防御性编程,看是否读取到了四字节
        {
            task_list[TaskIndex](); // 执行任务
        }
        else if (n == 0)
        {
            // 关闭写端,最后读取到0,最终子进程被杀掉,表示主程序不会再向管道输送内容了
            printf("父进程关闭写端,子进程结束\n");
            exit(READ_FAILD);
        }
        else
        {
            printf("子进程读取数据错误\n");
        }
        sleep(1);
    }
}

class ProcessPool
{
private:
    class Channel
    {
    public:
        Channel(int wfd, pid_t sub_pid) : _wfd(wfd), _sub_pid(sub_pid)
        {
        }
        ~Channel()
        {
        }
        void set_channel_name(std::string name)
        {
            _channel_name = name;
        }
        void PrintChannel()
        {
            printf("_wfd:%d, _sub_pid:%d, _channel_name: %s\n", _wfd, _sub_pid, _channel_name.c_str());
        }
        int get_wfd()
        {
            return _wfd;
        }
        pid_t get_sub_pid()
        {
            return _sub_pid;
        }

    private:
        int _wfd;
        pid_t _sub_pid;
        std::string _channel_name;
    };

public:
    void init(cb_t cb)
    {
        CreateProcessChannel(cb);
    }

    void Run()
    {
        int cnt = 10;
        while (cnt--)
        // while(true)
        {
            // 得到需要调用的channel--轮询的方式
            int ChannelIndex = SelectChannel();
            // 得到需要调用任务的下标
            int TaskIndex = SelectTask();
            // 发送任务码让子进程去执行
            SendTaskToSlaver(ChannelIndex, TaskIndex);
        }
    }

    void Quit()
    {
        // 关闭所有管道文件的写端,此时读管道的进程会被杀掉编程僵尸进程
        // 父进程回收子进程
        // version1 -- right
        // for (auto &channel : channels)
        // {
        //     close(channel.get_wfd());
        // }
        // for (auto &channel : channels)
        // {
        //     waitpid(channel.get_sub_pid(), NULL, 0);
        //     printf("%d子进程回收成功\n", channel.get_sub_pid());
        // }

        // version2 --wrong
        // fork拷贝父进程数据,而父进程的fd表已经被填充了
        // 第一个进程管道有5个进程指向，最后一个进程管道只有最后一个进程指向

        // 解决方案：保证一个管道只有一个进程指向,修改初始化时的代码
        for (auto &channel : channels)
        {
            close(channel.get_wfd());
            waitpid(channel.get_sub_pid(), NULL, 0);
        }

        // version3--right--修改顺序,最后一个管道只有一个进程指向,关闭之后，该进程fd释放,其余管道数量也会减1
        //  int n = channels.size();
        //  for(int i = n - 1; i >= 0; i--)
        //  {
        //      close(channels[i].get_wfd());
        //      waitpid(channels[i].get_sub_pid(), NULL, 0);
        //  }
    }
    void Debug()
    {
        for (auto &x : channels)
        {
            x.PrintChannel();
        }
    }

private:
    int SelectChannel() // 轮询方式选择channel
    {
        static int staticindex = 0; // 生命周期是全局的,防止选择时index重新创建栈空间值永远为0
        int value = staticindex % processnum;
        staticindex++;

        return value;
    }

    int SelectTask() // 随机数形式选择Task
    {
        int index = rand() % 4;

        return index;
    }

    void SendTaskToSlaver(int ChannelIndex, int TaskIndex) // 协议:规定读或写要求以四字节为单位,即task_list的下标
    {
        // 父进程向ChannelIndex下写数据
        // std::cout << sizeof TaskIndex << std::endl;
        ssize_t n = write(channels[ChannelIndex].get_wfd(), &TaskIndex, sizeof TaskIndex);
        if (n < 0)
        {
            perror("write faild\n");
        }
        printf("发送任务第%d任务给%d\n", TaskIndex, channels[ChannelIndex].get_sub_pid());
        sleep(1);
    }

    void CreateProcessChannel(cb_t cb)
    {
        for (int i = 0; i < processnum; i++)
        {
            // 创建管道文件
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
            {
                std::cerr << "pipe faild!" << std::endl;
                exit(PIPE_FAILD);
            }
            // fork子进程
            // 子关闭写，父关闭读
            // 进行数据传输
            pid_t pid = fork();
            if (pid < 0)
            {
                perror("fork faild!\n");
                exit(FORK_FAILD);
            }
            else if (pid == 0)
            { // child
                // 将之前指向其他管道的fd关闭
                if (!channels.empty())//保证一个管道只有一个进程指向
                {
                    for (auto &channel : channels)
                    {
                        close(channel.get_wfd());
                    }
                }

                close(pipefd[1]);
                cb(pipefd[0]);
                exit(OK);
            }
            else
            {
                // parent
                // 描述管道+子进程,再组织
                close(pipefd[0]);
                channels.emplace_back(pipefd[1], pid);
                printf("创建成功,子进程:%d\n", pid);
                int n = channels.size();
                channels[n - 1].set_channel_name("sub_process" + std::to_string(pid));
                sleep(1);
            }
        }
    }

private:
    std::vector<Channel> channels;
};

int main()
{
    // 种种子
    srand(time(NULL));
    // 创建多个管道和多个子进程
    // 初始化进程池
    ProcessPool pp;
    pp.init(Do_Task);
    pp.Debug();
    // 父进程控制子进程
    pp.Run();
    // 释放资源(回收子进程,关闭文件描述符)
    pp.Quit();
    return 0;
}